﻿#region Copyright (c) 2008 plexBB Team

/*  Copyright (c) 2008 plexBB Team
 *  
 *  This file is free software: you may copy, redistribute and/or modify it  
 *  under the terms of the GNU General Public License as published by the  
 *  Free Software Foundation, either version 2 of the License, or (at your  
 *  option) any later version.  
 *  
 *  This file is distributed in the hope that it will be useful, but  
 *  WITHOUT ANY WARRANTY; without even the implied warranty of  
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  
 *  General Public License for more details.  
 *  
 *  You should have received a copy of the GNU General Public License  
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Caching;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using PlexBB.Data;

namespace PlexBB.Controls.Admin
{
    public abstract class Acp : RootTemplate, IModuleContainer
    {
        private const string _modulesCacheKey = "plexBBAcpModules";
        private static readonly TimeSpan _modulesCacheInterval = TimeSpan.FromMinutes(1);
        private Module _currentModule;
        private ModuleItem _currentTab;
        private IEnumerable<ModuleItem> _modulesPlain;
        private ModuleItem[] _modulesTree;

        protected Acp()
        {
            Lang.Require("Acp/Common");
        }

        public Confirm Confirm { get; private set; }
        public Message Message { get; private set; }
        public Control ModuleControl { get; private set; }

        private ModuleItem[] ModulesTree
        {
            get
            {
                if (_modulesTree == null)
                {
                    // we need to store and check global version of modules to make sure that we'll not 
                    // use old cached items, especially for systems with several isolated worker 
                    // processes (like web farms, web gardens or simply multiCPU systems running several
                    // worker processes within a single application pool)
                    GlobalSetting setting = Data.GlobalSettings.SingleOrDefault(s => s.Name == GlobalSetting.CachedModulesVersion);
                    string version = setting == null ? null : setting.Value;

                    lock (_modulesCacheKey)
                    {
                        // get modules from cache if cache version is correct
                        ModulesCacheItem cacheItem = (ModulesCacheItem)Page.Cache[_modulesCacheKey];
                        if (cacheItem != null && cacheItem.Version == version)
                        {
                            _modulesTree = cacheItem.Modules;
                        }
                        // finally, get it from DB
                        if (_modulesTree == null)
                        {
                            _modulesTree = (from mTab in Data.Modules
                                            let mCats = (from mCat in mTab.Modules
                                                         let mItems = (from mItem in mCat.Modules
                                                                       where mItem.Enabled && mItem.Display
                                                                       orderby mItem.Position
                                                                       select new ModuleItem { Module = mItem, Level = 2 })
                                                         where mCat.Enabled && mCat.Display && mItems.Count() > 0
                                                         orderby mCat.Position
                                                         select new ModuleItem { Module = mCat, ChildModules = mItems, Level = 1 })
                                            where
                                                mTab.Enabled && mTab.Display && mTab.ModuleType == ModuleType.Acp && mTab.ParentID == null &&
                                                mCats.Count() > 0
                                            orderby mTab.Position
                                            select new ModuleItem { Module = mTab, ChildModules = mCats, Level = 0 }).ToArray();

                            cacheItem = new ModulesCacheItem { Modules = _modulesTree, Version = version };
                            Page.Cache.Insert(_modulesCacheKey, cacheItem, null, Cache.NoAbsoluteExpiration, _modulesCacheInterval,
                                              CacheItemPriority.Low, null);
                        }
                    }
                }
                return _modulesTree;
            }
        }

        private IEnumerable<ModuleItem> ModulesPlain
        {
            get
            {
                if (_modulesPlain == null)
                {
                    _modulesPlain = ModulesTree.Concat(ModulesTree.SelectMany(mi => mi.ChildModules)).
                        Concat(ModulesTree.SelectMany(mi => mi.ChildModules).SelectMany(mi => mi.ChildModules));
                }
                return _modulesPlain;
            }
        }

        private ModuleItem CurrentTab
        {
            get
            {
                if (_currentTab == null)
                {
                    _currentTab = ModulesTree.Single(mi => mi.Module.ModuleID == _currentModule.ParentModule.ParentID);
                }
                return _currentTab;
            }
        }

        #region Controls

        protected Control cContent;
        protected Repeater rCategories;
        protected Repeater rTabs;
        protected string TRANSLATION_INFO;
        protected ITextControl tUserName;

        #endregion

        #region Nested types

        #region Nested type: ModuleItem

        private class ModuleItem
        {
            public IEnumerable<ModuleItem> ChildModules;
            public int Level;
            public Module Module;
        }

        #endregion

        #region Nested type: ModulesCacheItem

        private class ModulesCacheItem
        {
            public ModuleItem[] Modules;
            public string Version;
        }

        #endregion

        #endregion

        #region IModuleContainer Members

        Module IModuleContainer.CurrentModule
        {
            get { return _currentModule; }
        }

        #endregion

        /// <summary>
        /// To be used by AcpModules only, when it alters PlexBB modules.
        /// </summary>
        public void InvalidateCachedModules()
        {
            _modulesTree = null;
            _modulesPlain = null;
            _currentTab = null;
        }

        protected override IAttributeAccessor GetRootControl()
        {
            return Page.Form;
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            #region Determine _currentModule

            string moduleIDString = PlexBB.RequestAction["mid"];
            if (moduleIDString != null)
            {
                int moduleID = int.Parse(moduleIDString);

                // select enabled module (also includes hidden modules)
                Module module = Data.Modules.SingleOrDefault(m => m.ModuleID == moduleID && m.Enabled);

                if (module != null)
                {
                    if (module.IsCategory)
                    {
                        // find moduleItem (this also ensures that our module is not hidden)
                        ModuleItem moduleItem = ModulesPlain.SingleOrDefault(m => m.Module.ModuleID == moduleID);
                        // accept only root modules for category
                        if (moduleItem != null && moduleItem.Level == 0)
                        {
                            _currentModule = moduleItem.ChildModules.First().ChildModules.First().Module;
                        }
                    }
                    else
                    {
                        _currentModule = module;
                    }
                }
            }
            if (_currentModule == null)
            {
                // first module of the first category of the first tab
                _currentModule = ModulesTree.First().ChildModules.First().ChildModules.First().Module;
            }

            #endregion

            // load and add module control
            ModuleControl = _currentModule.CreateControl();
            cContent.Controls.Add(ModuleControl);

            // also add Confirm and Message controls (invisible by default)
            Confirm = PlexBB.CreateForumControl<Confirm>();
            Confirm.Visible = false;
            cContent.Controls.Add(Confirm);

            Message = PlexBB.CreateForumControl<Message>();
            Message.Visible = false;
            cContent.Controls.Add(Message);

            // add jQuery reference
            HtmlControl jQueryScriptControl = new HtmlGenericControl("script");
            jQueryScriptControl.Attributes["type"] = "text/javascript";

            jQueryScriptControl.Attributes["src"] = PlexBB.ResolveBaseDirUrl("Images/jQuery.js");
            Page.Header.Controls.Add(jQueryScriptControl);
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            tUserName.Text = Server.HtmlEncode(User.UserName);

            // bind rTabs

            rTabs.EnableViewState = false;
            rTabs.DataSource = ModulesTree.Select(mi => mi.Module);
            rTabs.ItemDataBound += rTabs_ItemDataBound;
            rTabs.DataBind();

            // bind rCategories
            rCategories.EnableViewState = false;
            rCategories.DataSource = CurrentTab.ChildModules;
            rCategories.ItemDataBound += rCategories_ItemDataBound;
            rCategories.DataBind();

            //set title
            if (Page.Title.Length == 0)
            {
                Page.Title = Server.HtmlEncode(Lang[_currentModule.LangName]);
            }
        }

        private void rTabs_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            Module module = (Module)e.Item.DataItem;
            SetupItemDataLink(e.Item, module);
            if (module == CurrentTab.Module)
            {
                HtmlGenericControl cTab = e.Item.Find<HtmlGenericControl>("cTab");
                if (cTab != null)
                {
                    cTab.Attributes["class"] = "activetoptab";
                }
            }
        }

        private void rCategories_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            ModuleItem itemData = (ModuleItem)e.Item.DataItem;
            ITextControl lTitle = e.Item.Find<ITextControl>("lTitle");
            if (lTitle != null)
            {
                lTitle.Text = Server.HtmlEncode(Lang[itemData.Module.LangName]);
            }
            Repeater rItems = e.Item.Find<Repeater>("rItems");
            if (rItems != null)
            {
                rItems.DataSource = itemData.ChildModules;
                rItems.ItemDataBound += rItems_ItemDataBound;
                rItems.DataBind();
            }
        }

        private void rItems_ItemDataBound(object sender, RepeaterItemEventArgs e)
        {
            ModuleItem itemData = (ModuleItem)e.Item.DataItem;
            SetupItemDataLink(e.Item, itemData.Module);
            // using ModuleID because we can have different module objects 
            if (itemData.Module.ModuleID == _currentModule.ModuleID)
            {
                HtmlGenericControl cItem = e.Item.Find<HtmlGenericControl>("cItem");
                if (cItem != null)
                {
                    cItem.Attributes["class"] = "activemenu";
                }
            }
        }

        private void SetupItemDataLink(Control item, Module module)
        {
            HyperLink hlLink = item.Find<HyperLink>("hlLink");
            if (hlLink != null)
            {
                hlLink.NavigateUrl = PlexBB.ResolveActionUrl("acp", "mid", module.ModuleID.ToString());
            }

            ITextControl lName = item.Find<ITextControl>("lName");
            if (lName != null)
            {
                lName.Text = Server.HtmlEncode(Lang[module.LangName]);
            }
        }
    }
}
